package zdream.pmfield.test.stage.effect;

import org.junit.Before;
import org.junit.Test;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import zdream.pmfield.core.Result;
import zdream.pmfield.data.foe.BStat;
import zdream.pmfield.data.foe.Foe;
import zdream.pmfield.data.foe.MoveTemplate;
import zdream.pmfield.stage.StageBuilderForTest;
import zdream.pmfield.stage.StageWrapper;
import zdream.pmfield.stage.Timeline.TimelineItem;
import zdream.pmfield.stage.action.AttackAction;
import zdream.pmfield.stage.message.ActionRequestMessage;
import zdream.pmfield.stage.message.ActionStartMessage;
import zdream.pmfield.stage.message.AttackMessage;
import zdream.pmfield.stage.message.FoeBStatLevelChangeMessage;
import zdream.pmfield.stage.message.TurnFinishedMessage;
import zdream.pmfield.stage.message.TurnStartMessage;
import zdream.pmfield.stage.object.FoeActor;
import zdream.pmfield.storage.FoeTemplateStorage;
import zdream.pmfield.test.utils.ResourceConstant;
import zdream.pmfield.test.utils.StageConstant;

import static org.junit.Assert.*;

/**
 * @author Zdream
 * @date 2023-12-13
 * @since 0.0.1
 */
public class BStatLevelCase {
	FoeTemplateStorage storage;

	@Before
	public void before() {
		storage = new FoeTemplateStorage();
		storage.load();
		storage.loadMovesInResource(ResourceConstant.RES_PATH_TEST_MOVES_JSON);
	}

	@Test
	public void testBStatLevelChangeEffect() {
		MoveTemplate changeMove = storage.findMoveById(10000031, 1);
		assertEquals("对方攻击下降", changeMove.name);
		MoveTemplate physicalMove = storage.findMoveById(10000020, 1);
		assertEquals("物理的", physicalMove.name);
		MoveTemplate specialMove = storage.findMoveById(10000021, 1);
		assertEquals("特殊的", specialMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(95))
				.withLevel(30)
				.withMove(changeMove)
				.withMove(physicalMove)
				.skipShuffleVStat()
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(147))
				.withLevel(30)
				.withMove(physicalMove)
				.withMove(specialMove)
				.skipShuffleVStat()
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.disableDistanceCheck()
				.buildWrapper();

		System.out.println("TEST 1: 攻击下降");

		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);

		int originAT = wrapper.stage.findFoe(foe2).getBStat(BStat.AT);
		int originSA = wrapper.stage.findFoe(foe2).getBStat(BStat.SA);

		wrapper.runUntil(ActionStartMessage.class, message -> {
			assertTrue(message.action.isForAttack());
			final AttackAction attack = message.action.asAttack();

			assertEquals(foe1, attack.foe.foe);
			assertEquals(changeMove, attack.move.move);
			return true;
		});

		float[] lastUpdateTime = new float[] {0};
		wrapper.runUntil(FoeBStatLevelChangeMessage.class, message -> {
			assertEquals(foe2, message.foe.foe);
			assertEquals(2, message.items.size());
			assertEquals(StageConstant.EFFECT_BSTAT_CHANGE, message.title);

			for (FoeBStatLevelChangeMessage.LevelUpdateItem item : message.items) {
				final BStat bStat = item.bStat;
				assertTrue(bStat == BStat.AT || bStat == BStat.SA);
				assertEquals(-1, item.level);
				assertTrue(item.change);

				assertNull(item.rejectBy);
				assertTrue(item.accept);

				assertEquals(message.foe.getBStatLevel(bStat), item.levelTo);
				assertEquals(item.levelFrom - 1, item.levelTo);
			}
			lastUpdateTime[0] = wrapper.now();
			return true;
		});
		wrapper.runUntil(TurnFinishedMessage.class);

		System.out.println("TEST 2: 攻击下降效果存在时, 攻击方攻击变小");
		assertTrue(wrapper.stage.findFoe(foe2).getBStat(BStat.AT) < originAT);
		System.out.println("AT: " + originAT + " -> " + wrapper.stage.findFoe(foe2).getBStat(BStat.AT));

		assertTrue(wrapper.stage.findFoe(foe2).getBStat(BStat.SA) < originSA);
		System.out.println("SA: " + originSA + " -> " + wrapper.stage.findFoe(foe2).getBStat(BStat.SA));

		System.out.println("TEST 3: 攻击下降如果不再继续改变, 那么三回合后能力将恢复一级");
		wrapper.clearOnMessageHooks();
		wrapper.onDefaultActionRequest();
		wrapper.onActionRequest(wrapper.createDefaultController().withMove(physicalMove), foe1);

		wrapper.runUntil(FoeBStatLevelChangeMessage.class, message -> {
			assertEquals(foe2, message.foe.foe);
			assertEquals(2, message.items.size());
			assertEquals(StageConstant.BSTAT_LEVEL_TITLE_RESTORE, message.title);

			for (FoeBStatLevelChangeMessage.LevelUpdateItem item : message.items) {
				final BStat bStat = item.bStat;
				assertTrue(bStat == BStat.AT || bStat == BStat.SA);
				assertEquals(0, item.level);
				assertTrue(!item.change);

				assertNull(item.rejectBy);
				assertTrue(item.accept);
				assertEquals(0, item.levelTo);
			}

			final float now = wrapper.now();
			assertTrue(now - lastUpdateTime[0] >= 3);
			assertTrue(now - lastUpdateTime[0] < 4);
			return true;
		});

		assertEquals(wrapper.stage.findFoe(foe2).getBStat(BStat.AT), originAT);
		assertEquals(wrapper.stage.findFoe(foe2).getBStat(BStat.SA), originSA);
	}

	@Test
	public void testTimelineUpdateWhenSpeedChanged() {
		MoveTemplate changeMove = storage.findMoveById(10000032, 1);
		assertEquals("自己速度上升", changeMove.name);
		MoveTemplate normalMove = storage.findMoveById(10000015, 1);
		assertEquals("无效果", normalMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(85))
				.withLevel(30)
				.withMove(changeMove)
				.skipShuffleVStat()
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(128))
				.withLevel(30)
				.withMove(normalMove)
				.skipShuffleVStat()
				.build();
		assertEquals(foe1.getBStat(BStat.SP), foe2.getBStat(BStat.SP));

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.disableDistanceCheck()
				.buildWrapper();

		List<TimelineItem> items = wrapper.stage.timeline.createExpectOrder();
		assertTrue(items.stream().allMatch(TimelineItem::isForFoe));
		long count1 = items.stream()
				.map(TimelineItem::asFoe)
				.filter(item -> item.foe.foe == foe1)
				.count();
		long count2 = items.stream()
				.map(TimelineItem::asFoe)
				.filter(item -> item.foe.foe == foe2)
				.count();
		assertEquals(count1, count2);
		FoeActor foe1Actor = wrapper.stage.findFoe(foe1);
		FoeActor foe2Actor = wrapper.stage.findFoe(foe2);
		assertEquals(0.00001, foe1Actor.speedCost, foe2Actor.speedCost);

		System.out.println("TEST 1: 速度上升");

		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);
		wrapper.runUntil(ActionStartMessage.class, message -> {
			assertTrue(message.action.isForAttack());
			assertEquals(foe1, message.action.asAttack().foe.foe);
			assertEquals(changeMove, message.action.asAttack().move.move);
			return true;
		});
		wrapper.runUntil(FoeBStatLevelChangeMessage.class, message -> {
			assertEquals(foe1, message.foe.foe);
			assertEquals(1, message.items.size());
			assertTrue(message.items.get(0).accept);
			assertEquals(BStat.SP, message.items.get(0).bStat);
			assertEquals(2, message.items.get(0).level);
			assertTrue(message.items.get(0).change);
			assertNull(message.items.get(0).rejectBy);
			assertEquals(0, message.items.get(0).levelFrom);
			assertEquals(2, message.items.get(0).levelTo);
			return true;
		});

		System.out.println("TEST 2: 速度上升后行动序列变化");
		items = wrapper.stage.timeline.createExpectOrder();
		System.out.println(items);
		assertTrue(items.stream().allMatch(TimelineItem::isForFoe));
		count1 = items.stream()
				.map(TimelineItem::asFoe)
				.filter(item -> item.foe.foe == foe1)
				.count();
		count2 = items.stream()
				.map(TimelineItem::asFoe)
				.filter(item -> item.foe.foe == foe2)
				.count();
		assertTrue(count1 > count2);
		assertTrue(foe1Actor.speedCost < foe2Actor.speedCost);
	}

	@Test
	public void testDefenseLevelChangeEffect() {
		MoveTemplate changeMove = storage.findMoveById(10000033, 1);
		assertEquals("自己防御上升", changeMove.name);
		MoveTemplate physicalMove = storage.findMoveById(10000020, 4);
		assertEquals("物理的", physicalMove.name);
		MoveTemplate specialMove = storage.findMoveById(10000021, 4);
		assertEquals("特殊的", specialMove.name);
		MoveTemplate normalMove = storage.findMoveById(10000015, 1);
		assertEquals("无效果", normalMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(1))
				.withLevel(30)
				.withMove(normalMove)
				.withMove(changeMove)
				.skipShuffleVStat()
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(normalMove)
				.withMove(physicalMove)
				.withMove(specialMove)
				.skipShuffleVStat()
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.disableDistanceCheck()
				.buildWrapper();

		System.out.println("TEST 1: 记录原始伤害数据");

		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe2);

		int originDF = wrapper.stage.findFoe(foe1).getBStat(BStat.DF);
		int originSD = wrapper.stage.findFoe(foe1).getBStat(BStat.SD);
		AtomicInteger atDamage = new AtomicInteger();
		AtomicInteger saDamage = new AtomicInteger();

		wrapper.clearOnMessageHooks();
		wrapper.onMessage(ActionRequestMessage.class, message -> {
			assertEquals(foe2, message.foe.foe);
			Result result = message.createReplyTemplate()
					.withTarget(foe1)
					.withMove(physicalMove)
					.commit();
			assertTrue(result.errorMessage, result.success);
		});
		wrapper.runUntil(ActionStartMessage.class);
		wrapper.runUntil(AttackMessage.class, message -> {
			assertEquals(1, message.report.items.size());
			atDamage.set(message.report.items.get(0).damage);
			assertTrue(atDamage.get() > 0);
			return true;
		});

		wrapper.clearOnMessageHooks();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe2);

		wrapper.clearOnMessageHooks();
		wrapper.onMessage(ActionRequestMessage.class, message -> {
			assertEquals(foe2, message.foe.foe);
			Result result = message.createReplyTemplate()
					.withTarget(foe1)
					.withMove(specialMove)
					.commit();
			assertTrue(result.errorMessage, result.success);
		});
		wrapper.runUntil(ActionStartMessage.class);
		wrapper.runUntil(AttackMessage.class, message -> {
			assertEquals(1, message.report.items.size());
			saDamage.set(message.report.items.get(0).damage);
			assertTrue(saDamage.get() > 0);
			return true;
		});

		System.out.println("TEST 2: 防御上升");

		wrapper.clearOnMessageHooks();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);

		wrapper.clearOnMessageHooks();
		wrapper.onMessage(ActionRequestMessage.class, message -> {
			assertEquals(foe1, message.foe.foe);
			Result result = message.createReplyTemplate()
					.withTarget(foe1)
					.withMove(changeMove)
					.commit();
			assertTrue(result.errorMessage, result.success);
		});
		wrapper.runUntil(FoeBStatLevelChangeMessage.class, message -> {
			assertEquals(foe1, message.foe.foe);
			assertEquals(2, message.items.size());
			assertEquals(StageConstant.EFFECT_BSTAT_CHANGE, message.title);

			for (FoeBStatLevelChangeMessage.LevelUpdateItem item : message.items) {
				final BStat bStat = item.bStat;
				assertTrue(bStat == BStat.DF || bStat == BStat.SD);
				assertEquals(1, item.level);
				assertTrue(item.change);

				assertNull(item.rejectBy);
				assertTrue(item.accept);

				assertEquals(message.foe.getBStatLevel(bStat), item.levelTo);
				assertEquals(item.levelFrom + 1, item.levelTo);
			}
			return true;
		});

		int nowDF = wrapper.stage.findFoe(foe1).getBStat(BStat.DF);
		int nowSD = wrapper.stage.findFoe(foe1).getBStat(BStat.SD);
		assertTrue(nowDF > originDF);
		assertTrue(nowSD > originSD);

		wrapper.runUntil(TurnFinishedMessage.class);

		System.out.println("TEST 3: 防御上升效果存在时, 攻击方攻击变小");

		wrapper.clearOnMessageHooks();
		wrapper.onMessage(ActionRequestMessage.class, message -> {
			assertEquals(foe2, message.foe.foe);
			Result result = message.createReplyTemplate()
					.withTarget(foe1)
					.withMove(physicalMove)
					.commit();
			assertTrue(result.errorMessage, result.success);
		});
		wrapper.runUntil(ActionStartMessage.class);
		wrapper.runUntil(AttackMessage.class, message -> {
			assertEquals(1, message.report.items.size());
			assertTrue(message.report.items.get(0).damage < atDamage.get());
			return true;
		});

		wrapper.clearOnMessageHooks();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe2);

		wrapper.clearOnMessageHooks();
		wrapper.onMessage(ActionRequestMessage.class, message -> {
			assertEquals(foe2, message.foe.foe);
			Result result = message.createReplyTemplate()
					.withTarget(foe1)
					.withMove(specialMove)
					.commit();
			assertTrue(result.errorMessage, result.success);
		});
		wrapper.runUntil(ActionStartMessage.class);
		wrapper.runUntil(AttackMessage.class, message -> {
			assertEquals(1, message.report.items.size());
			assertTrue(message.report.items.get(0).damage < saDamage.get());
			return true;
		});
	}

	@Test
	public void testStartStageWithLevelChangedAbility() {
		MoveTemplate normalMove = storage.findMoveById(10000015, 1);
		assertEquals("无效果", normalMove.name);

		Foe foe1 = Foe.builder(storage.findFoeById(4))
				.withLevel(30)
				.withMove(normalMove)
				.withAbilityScripts("b-stat-change/self/AT/1")
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(1))
				.withLevel(30)
				.withMove(normalMove)
				.build();

		final StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.buildWrapper();
		wrapper.printBeforeHandleMessage();
		wrapper.onMessage(TurnStartMessage.class, message -> {
			throw new RuntimeException("FoeBStatLevelChangeMessage 应该比 TurnStartMessage 早触发");
		});
		wrapper.runUntil(FoeBStatLevelChangeMessage.class, message -> {
			assertEquals(foe1, message.foe.foe);
			assertEquals(1, message.items.size());
			assertTrue(message.items.get(0).accept);
			assertEquals(BStat.AT, message.items.get(0).bStat);
			assertEquals(1, message.items.get(0).level);
			assertTrue(message.items.get(0).change);
			assertNull(message.items.get(0).rejectBy);
			assertEquals(0, message.items.get(0).levelFrom);
			assertEquals(1, message.items.get(0).levelTo);
			return true;
		});

		wrapper.clearOnMessageHooks();
		wrapper.runUntil(TurnStartMessage.class);
		assertEquals(1, wrapper.stage.findFoe(foe1).findOriginalBuff().getBStatLevel(BStat.AT));
	}
}
